package com.opennews.openplatform

import com.opennews.openplatform.constants.RoleConstant
import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import groovy.transform.CompileDynamic
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.sql.JoinType

@Transactional(readOnly = true)
@GrailsCompileStatic
class AccountGroupService {
    FileService fileService
    IncomeExpenseDetailTypeService incomeExpenseDetailTypeService
    RoleService roleService
    UserService userService
    UserActiveTokenService userActiveTokenService

    /**
     * Queries user's account group info by user account group id.
     * @param accountGroupId: The string of the user account group id.
     * @return The map instance which contains keys below.
     *              id: String of the user account group id.
     *              title: String of the title.
     */
    @CompileDynamic
    Map<String, ?> queryAccountGroupInfo(String accountGroupId, String userId) {
        def owner = AccountGroupUser.createCriteria().get {
            // Transforms the result data format to Map instance.
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            // Sets the join type for user property in order to get its properties.
            createAlias("user", "user", JoinType.INNER_JOIN)

            // Sets the query condition.
            and {
                eq("accountGroup.id", accountGroupId)
                eq("isAccountGroupCreator", true)
            }

            // Specifies the property we need.
            projections {
                property("user.id", "id")
                property("user.username", "username")
                property("user.fullName", "fullName")
            }
        } as Map<String, ?>

        return [
            id: accountGroupId,
            isOwner: owner.id == userId,
            ownerId: owner.id,
            ownerUsername: owner.username,
            ownerFullName: owner.fullName
        ]
    }

    /**
     * Creates a new AccountGroup and a new User.
     * This typically used by new user register.
     * During register the username and password are only needed.
     * The other details can be added later.
     * @param username: The username used for login.
     * @param password: The password used for login.
     * @param fullName: The full name used for display.
     * @return The created User.
     */
    @Transactional
    User register(String username, String password, String fullName) {
        // Gets the ACCOUNT_GROUP_ADMIN role id.
        def roleId = roleService.queryId(RoleConstant.ROLE_ACCOUNT_GROUP_ADMIN)

        // Creates a new AccountGroup which will be used by the new User.
        def accountGroup = new AccountGroup(title: "我的账户").save()

        // Prepares the default income/expense detail type for the created account group.
        incomeExpenseDetailTypeService.init(accountGroup.id)

        // Creates a new User with the created AccountGroup and username, password.
        def user = new User().with {
            it.accountGroup = accountGroup
            it.username = username
            it.password = password
            it.fullName = fullName
            it.save()
        }

        // Defines relationship between account group and user.
        new AccountGroupUser().with {
            it.accountGroup = accountGroup
            it.user = user
            it.isAccountGroupCreator = true
            it.save()
        }

        // Assigns the default role to the created user.
        new UserRole().with {
            it.accountGroup = accountGroup
            it.user = user
            it.role = Role.proxy(roleId)
            it.save()
        }

        return user
    }

    /**
     * IMPORTANT!!!
     * THIS ACTION CANNOT BE UNDONE.
     * Clears own account group, including users and all data and quits from all other account groups.
     * @param accountGroupId: String of user account group id.
     * @param username: String of username of the account group admin.
     */
    @Transactional
    @CompileDynamic
    void unregister(String userId) {
        // Queries user's avatar filename.
        String avatarFilename = User.where {
            id == userId
        }.projections {
            property("avatarFilename")
        }.get()

        // Queries user's own account group id.
        String accountGroupId = AccountGroupUser.where {
            user == User.proxy(userId) &&
                isAccountGroupCreator == true
        }.projections {
            property("accountGroup.id")
        }.get()

        def accountGroup = AccountGroup.proxy(accountGroupId)
        def user = User.proxy(userId)

        // All users with this account group as their current account group will be restored to their own.
        userService.restoreAccountGroupByAccountGroupId(accountGroupId)

        // Deletes active tokens which currently logged in to this account group to force re-login.
        userActiveTokenService.deleteByAccountGroup(accountGroupId)

        // Removes current user from all other account groups.
        // Removes all users from this account group.
        AccountGroupUser.where { accountGroup == accountGroup || user == user }.deleteAll()

        // Clears all the data of this account group.
        JourneyIncomeExpenseDetail.where {
            accountGroup == accountGroup
        }.deleteAll()

        Journey.where {
            accountGroup == accountGroup
        }.deleteAll()

        IncomeExpenseDetail.where {
            accountGroup == accountGroup
        }.deleteAll()

        IncomeExpenseDetailType.where {
            accountGroup == accountGroup
        }.deleteAll()

        BankCard.where {
            accountGroup == accountGroup
        }.deleteAll()

        UserRole.where {
            accountGroup == accountGroup ||
                user == user
        }.deleteAll()

        // Deletes all its pending requests.
        JoinAccountGroupRequest.where {
            accountGroup == accountGroup ||
                receiver == user ||
                sender == user ||
                owner == user
        }.deleteAll()

        // Deletes current user.
        user.delete()

        // Deletes this account group.
        accountGroup.delete()

        // Deletes avatar file if exists.
        if (!SharedUtil.isNullOrEmpty(avatarFilename)) {
            fileService.delete(avatarFilename)
        }
    }

    /**
     * Updates account group info based on provided map data and its id.
     * The map keys must match account group property names.
     * If the map item will be ignored if its value is null.
     * @param data: The map which contains keys with same name as account group properties.
     * The map must contains key of id.
     */
    @Transactional
    void update(Map<String, ?> data) {
        SharedUtil.updateDomainClass(AccountGroup, data)
    }
}